在Java的世界中,輸入/輸出(I/O)操作一直是程式設計中的重要組成部分。隨著Java的發展,傳統的阻塞式I/O(Blocking I/O)已經無法滿足現代應用程式對高效能和可擴展性的需求。
為解決這個問題,Java在1.4版本中引入新的I/O(NIO,New I/O)API。
NIO(New I/O)是Java 1.4版本引入的一套新的I/O API,用於替代標準的Java I/O和Java Networking API。
NIO的設計目標是為提供更高效的I/O操作,特別是在處理大量數據和高併發場景時。
NIO的核心概念包括:
Buffer(緩衝區):一個用於存儲數據的容器,可以讀取和寫入數據。
Channel(通道):一個用於連接I/O操作源或目標的管道。
Selector(選擇器):允許單個線程監視多個Channel的I/O事件。
NIO的主要特點包括:
非阻塞I/O:允許線程在等待I/O操作完成時執行其他任務,提高系統的整體效率。
面向緩衝區:數據總是從通道讀取到緩衝區,或從緩衝區寫入到通道,提供更好的數據處理控制。
選擇器:允許單個線程管理多個通道,提高多路複用的能力。
直接緩衝區:支持直接記憶體分配的緩衝區,可以顯著提高某些I/O操作的性能。
記憶體映射文件:允許將文件直接映射到記憶體中,提供更高效的大文件處理能力。
NIO的引入使得Java能夠更好地處理高負載、高併發的網絡應用,如Web服務器、數據庫連接池等,不僅提高I/O操作的效率,還為開發者提供更靈活的I/O處理方式。
Buffer是NIO中的核心概念之一,是一個用於存儲特定基本類型數據的容器。
在NIO中,所有數據的讀取和寫入都要通過Buffer來進行。
Buffer本質上是一個記憶體塊,可以寫入數據,之後再讀取。Buffer對象內部維護一個數組,並提供一組方法來操作這個數組。
Buffer的工作模式通常包括以下步驟:
flip()
方法clear()
方法或compact()
方法Java NIO提供以下幾種主要的Buffer類型:
ByteBuffer是最常用的Buffer類型,可以與Channel直接交互。
Buffer類維護三個重要的狀態變量:
capacity:Buffer能夠容納的數據元素的最大數量。在Buffer創建時被設定,並且不能改變。
position:下一個要讀取或寫入的數據元素的索引。初始值為0,最大值為capacity-1。
limit:第一個不能讀取或寫入的數據元素的索引。在寫模式下,limit等於capacity;在讀模式下,limit表示可讀取的數據量。
allocate():創建一個Buffer對象。例如:ByteBuffer buf = ByteBuffer.allocate(1024);
put():向Buffer中寫入數據。例如:buf.put((byte) 'a');
flip():將Buffer從寫模式切換到讀模式。會將position設為0,limit設為之前的position。
get():從Buffer中讀取數據。例如:byte b = buf.get();
clear():清空整個Buffer,將position設為0,limit設為capacity。
compact():清空已經讀過的數據,將未讀的數據移到Buffer的開始處。
rewind():將position設為0,limit保持不變,允許重新讀取Buffer中的所有數據。
mark() 和 reset():標記當前position,之後可以通過reset()方法恢復到這個位置。
Channel是NIO中另一個核心概念,代表與I/O設備(如文件、網絡套接字)的連接。
Channel可以看作是數據的源頭或目的地,所有數據都通過Channel在Buffer和I/O設備之間傳輸。
Channel與流(Stream)的概念類似,但有以下幾個重要區別:
Java NIO提供多種Channel的實現,主要包括:
打開Channel:
FileChannel channel = FileChannel.open(Paths.get("file.txt"), StandardOpenOption.READ);
從Channel讀取數據:
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = channel.read(buf);
向Channel寫入數據:
String newData = "New String to write to file...";
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining()) {
channel.write(buf);
}
關閉Channel:
channel.close();
將數據從一個Channel傳輸到另一個Channel:
fromChannel.transferTo(0, fromChannel.size(), toChannel);
使用Selector監控多個Channel:
Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
從Channel讀取數據到Buffer:
從Buffer寫入數據到Channel:
以下是一個簡單的例子,展示如何使用FileChannel和ByteBuffer來讀取文件內容:
FileChannel channel = FileChannel.open(Paths.get("example.txt"), StandardOpenOption.READ);
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (channel.read(buffer) != -1) {
buffer.flip();
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
buffer.clear();
}
channel.close();
在這個例子中:
buffer.flip()
來準備讀取Buffer中的數據。buffer.clear()
來準備下一次讀取。高效的數據傳輸:Channel和Buffer之間的數據傳輸是直接的,沒有額外的複製操作,這提高I/O操作的效率。
靈活的數據處理:Buffer允許我們在寫入Channel之前對數據進行操作,例如加密、壓縮等。
非阻塞I/O:通過使用Selector,我們可以同時監控多個Channel的I/O事件,實現非阻塞I/O。
直接記憶體訪問:使用DirectByteBuffer可以實現零拷貝,進一步提高性能。
批量數據傳輸:Channel的transferTo()
和transferFrom()
方法允許高效的通道間數據傳輸。
Java NIO(New I/O)和傳統IO(Blocking I/O)在設計理念和實現方式上有顯著的差異。
傳統IO:
NIO:
本篇文章同步刊載: JYI.TW
筆者個人的網站: JUNYI